﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace HovisPresent.Net
{
	public delegate void SlaveConnected(string host);
	public delegate void SlaveClosed(string host);
	public delegate void SlaveMessageReceived(string host, SlaveMessage message, string[] parameters);

	/// <summary>
	/// Manages connections to a bunch of slaves
	/// </summary>
	class SlaveController : Connection
	{
		private Dictionary<string,ClientConnection> slaves;

		public event SlaveConnected OnSlaveConnected;
		public event SlaveClosed OnSlaveClosed;
		public event SlaveMessageReceived OnSlaveMessageReceived;

		public int Port { get; set; }

		public SlaveController(int port) {
			Port = port;
		}

		/// <summary>
		/// Gets the number of slaves.
		/// </summary>
		/// <value>The slave count.</value>
		public int SlaveCount {
			get {
				return slaves == null ? 0 : slaves.Count;
			}
		}

		/// <summary>
		/// Opens the connections.
		/// </summary>
		/// <param name="slavesMachines">The slaves machines.</param>
		public void Open(List<string> slaveMachines ) {
			slaves = new Dictionary<string,ClientConnection> (slaveMachines.Count);

			foreach (string slave in slaveMachines) {
				if (slaves.ContainsKey(slave))
					continue;
				ClientConnection connection = new ClientConnection();
				slaves[slave] = connection;

				try {
					connection.OnClientClosed += new ClientClosed(OnClientClosed);
					connection.OnClientMessageRecieved += new ClientMessageRecieved(OnClientMessageReceived);
					connection.OnClientConnected += new ClientConnected(OnClientConnected);
					connection.OnClientConnectFailed += new ClientConnectFailed(OnClientConnectFailed);
					connection.Connect(slave, Port);
				} catch (Exception e) {
					Debug.WriteLine(e.ToString());
				}
			}
		}

		/// <summary>
		/// Called when [client connect failed].
		/// </summary>
		/// <param name="connection">The connection.</param>
		/// <param name="e">The e.</param>
		void OnClientConnectFailed(ClientConnection connection, Exception e) {
			Debug.WriteLine("Connection to slave " + connection.Host + " failed:" + e.Message);
			RetrySlaveConnection(connection);
		}

		/// <summary>
		/// Retries the slave connection if it has failed or been closed before.
		/// </summary>
		/// <param name="connection">The connection.</param>
		public void RetrySlaveConnection(ClientConnection connection) {
			Debug.WriteLine("Retrying slave connection to " + connection.Host);
			connection.CloseSocket();
			connection.Connect(connection.Host, Port);
		}

		/// <summary>
		/// Retries the slave connection if it has failed or been closed before.
		/// </summary>
		/// <param name="connection">The connection.</param>
		public void RetrySlaveConnection(string host) {
			if ( !slaves.ContainsKey(host) )
				return;

			RetrySlaveConnection(slaves[host]);
		}

		/// <summary>
		/// Called when [client connected].
		/// </summary>
		/// <param name="connection">The connection.</param>
		void OnClientConnected(ClientConnection connection) {
			if (OnSlaveConnected != null)
				OnSlaveConnected(connection.Host);
		}

		/// <summary>
		/// Called when [client closed].
		/// </summary>
		/// <param name="connection">The connection.</param>
		void OnClientClosed(ClientConnection connection) {
			if (OnSlaveClosed!=null)
				OnSlaveClosed(connection.Host);
		}

		/// <summary>
		/// Called when [client message recieved].
		/// </summary>
		/// <param name="connection">The connection.</param>
		/// <param name="parameters">The parameters.</param>
		void OnClientMessageReceived(ClientConnection connection, string text) {

			Debug.WriteLine("Received slave text message("+connection.Host+") : " + text);

			string[] parameters = SplitMessage(text);
			if (parameters == null) {
				Debug.WriteLine("Invalid master message format");
//				SendInvalidCommand();
				return;
			}

			if (!Enum.IsDefined(typeof(SlaveMessage), parameters[0])) {
				Debug.WriteLine("Invalid slave message");
//				SendInvalidCommand();
				return;
			}

			SlaveMessage message = (SlaveMessage)Enum.Parse(typeof(SlaveMessage), parameters[0]);

			if (OnSlaveMessageReceived != null)
				OnSlaveMessageReceived(connection.Host, message, parameters);
		}

		/// <summary>
		/// Closes this instance.
		/// </summary>
		public void Close() {
			if (slaves == null)
				return;

			OnSlaveClosed = null;
			OnSlaveConnected = null;
			OnSlaveMessageReceived = null;

			foreach (ClientConnection connection in slaves.Values) {
				connection.Close();
			}
			slaves = null;
		}

		/// <summary>
		/// Sends the message to one or all slaves
		/// </summary>
		/// <param name="slave">The slave or null for all slaves</param>
		/// <param name="m">The m.</param>
		/// <param name="p">The p.</param>
		protected internal void SendMessage( string slave, MasterMessage m, params object[] p ) {
			string message = CreateMessage(m,p);

			if (slave == null) {
				foreach (ClientConnection slaveConnection in slaves.Values) {
					if (slaveConnection != null)
						slaveConnection.SendMessage(message);
				}
			} else {
				if (!slaves.ContainsKey(slave))
					return;
				ClientConnection slaveConnection = slaves[slave];
				slaveConnection.SendMessage(message);
			}

		}

		/// <summary>
		/// Sends the stop command.
		/// </summary>
		public void SendStop() {
			SendMessage(null, MasterMessage.Stop);
		}

		/// <summary>
		/// Sends the stop command to a particular slave
		/// </summary>
		/// <param name="slave">The slave.</param>
		public void SendStop(string slave) {
			SendMessage(slave, MasterMessage.Stop);
		}

		/// <summary>
		/// Sends the change slide.
		/// </summary>
		/// <param name="currentFilename">The current filename.</param>
		/// <param name="nextFilenameOrPreview">The next filename or preview.</param>
		/// <param name="script">The script.</param>
		public void SendChangeSlide(string currentFilename, string nextFilenameOrPreview, string script) {
			SendMessage( null, MasterMessage.ChangeSlide, currentFilename, nextFilenameOrPreview, script);
		}

		/// <summary>
		/// Sends the change slide command to a particular slave
		/// </summary>
		/// <param name="slave">The slave.</param>
		/// <param name="currentFilename">The current filename.</param>
		/// <param name="nextFilenameOrPreview">The next filename or preview.</param>
		/// <param name="script">The script.</param>
		public void SendChangeSlide(string slave, string currentFilename, string nextFilenameOrPreview, string script) {
			SendMessage(null, MasterMessage.ChangeSlide, currentFilename, nextFilenameOrPreview, script);
		}


		/// <summary>
		/// Creates the message string
		/// </summary>
		/// <param name="m">The m.</param>
		/// <param name="p">The p.</param>
		/// <returns></returns>
		public static string CreateMessage(MasterMessage m, params object[] p) {
			return CreateMessage(m.ToString(), p);
		}
	}
}
